package edu.wpi.cs509.ui.simulators;


import java.util.Random;

import edu.wpi.cs509.common.exceptions.InvalidLocation;
import edu.wpi.cs509.representation.Location;
import edu.wpi.cs509.representation.PlayerPiece;
import edu.wpi.cs509.representation.StrategoPiece;
import edu.wpi.cs509.representation.TeamColor;
import edu.wpi.cs509.ui.intfc.GameEventNotify;


/**
 * The game object used by the UI Client Logic simulator
 * 
 * @author afoltan
 *
 */
public class ServerGame {

	// for randomization
	private static final Random R = new Random();
	
	// game info
	private long gameID;
	
	// the game board representation
	// the board square in the upper left corner is the [0,0] location
	// the board square in the bottom right corner is the [9,9] location
	// player 1 is on the bottom
	// player 2 is on the top
	private static final int ROWS = 10;
	private static final int COLS = 10;
	private PlayerPiece gameboard[][] = new PlayerPiece[ROWS][COLS];
	
	private ClientGame player1 = null;
	private ClientGame player2 = null;
	
	private TeamColor p1Color, p2Color;
	
	private ClientGame whoseTurn = null;
	
	/**
	 * Initialize
	 */
	private void init() {
		this.gameID = R.nextLong();
		
		// Assign colors to players
		int r = (R.nextInt() & 0x1);
		this.p1Color = (r == 0) ? TeamColor.BLUE : TeamColor.RED;
		this.p2Color = (r == 1) ? TeamColor.BLUE : TeamColor.RED;
		System.out.println("P1 is "+p1Color.getName()+", P2 is "+p2Color.getName());
		
		// initialize the game board with no pieces
		for (int i=0; i<ROWS;i++) {
			for (int j=0; j<COLS;j++) {
				gameboard[i][j] = null;
			}
		}

	}
	
	/**
	 * Constructor
	 * @param notify - game event notification handler
	 */
	public ServerGame() {
		init();
	}

	/**
	 * Retrieve the ClientGame instance for player1
	 * @return
	 */
	public ClientGame getPlayer1() {
		return player1;
	}
		
	/**
	 * Retrieve the ClientGame instance for player1
	 * @return
	 */
	public ClientGame getPlayer2() {
		return player2;
	}
	
	/**
	 * Join the game as player 1
	 * @param simSession
	 * @param notify
	 */
	public ClientGame joinAsPlayer1( UserSessionSim p1, GameEventNotify notify ) {
		player1 = new ClientGame(this,p1, notify );
		if (p1Color == TeamColor.RED) {
			whoseTurn = player1;
		}
		return player1;
	}

	/**
	 * Join the game as player2
	 * @param simSession
	 * @param notify
	 */
	public ClientGame joinAsPlayer2( UserSessionSim p2, GameEventNotify notify ) {
		player2 = new ClientGame(this,p2,notify);
		if (p2Color == TeamColor.RED) {
			whoseTurn = player2;
		}
		return player2;
	}

	public long getGameID() {
		return gameID;
	}

	public void putPiece(Location loc, PlayerPiece piece) {
		piece.setLocation(loc);
		gameboard[loc.getXCoordinate()][loc.getYCoordinate()] = piece;
	}
	
	private void changeTurnAndNotify() {
		
		if (whoseTurn == player1) {
			whoseTurn = player2;
		} else {
			whoseTurn = player1;
		}
		
		whoseTurn.getNotify().notifyItsYourTurn();
		
	}

	public void removePiece(Location loc) {
		PlayerPiece piece = gameboard[loc.getXCoordinate()][loc.getYCoordinate()];
		if (piece != null) {
			piece.setLocation(null);
		}
		gameboard[loc.getXCoordinate()][loc.getYCoordinate()] = null;
	}

	/**
	 * When one player readies-up, the opponent must be informed. 
	 * @param clientGame
	 */
	public void signalPlayerReady(ClientGame clientGame) {
		if (clientGame == player1) {
			if (player2 != null) {
				player2.notifyOpponentReady();
			}
		} else if (clientGame == player2) {
			if (player1 != null) {
				player1.notifyOpponentReady();
			}
		} 
		
		if ((player1 != null) && player1.isReady() && 
			(player2 != null) && player2.isReady()) {
			whoseTurn.getNotify().notifyItsYourTurn();
		}
	}
	
	public boolean isSquareEmpty(Location loc) throws InvalidLocation {
		if ((loc.getXCoordinate() > COLS) || (loc.getYCoordinate() > ROWS)) {
			throw new InvalidLocation("location is off board ("+loc.getXCoordinate()+","+loc.getYCoordinate()+")" );
		}
		
		return (gameboard[loc.getXCoordinate()][loc.getYCoordinate()] == null);
	}

	public PlayerPiece getBoardPiece(Location loc) {
		return gameboard[loc.getXCoordinate()][loc.getYCoordinate()];
	}

	public boolean isJoinable() {
		return ((this.player1 == null) || (this.player2 == null));
	}

	public boolean isObservable() {
		return ((this.player1 != null) && (this.player2 != null));
	}

	public TeamColor getTeamColor(ClientGame clientGame) {
		if (clientGame == player1) {
			return p1Color;
		} else {
			return p2Color;
		}
	}

	public TeamColor getOpponentColor(ClientGame clientGame) {
		if (clientGame == player1) {
			return p2Color;
		} else {
			return p1Color;
		}
	}

	public String getOpponentName(ClientGame clientGame) {
		if (clientGame == player1) {
			return getPlayer2Name();
		} else {
			return getPlayer1Name();
		}
	}

	String getPlayer1Name() {
		if (player1 != null) {
			return player1.getMyName();
		} else {
			return "Player1";
		}
	}

	String getPlayer2Name() {
		if (player2 != null) {
			return player2.getMyName();
		} else {
			return "Player2";
		}
	}

	public GameEventNotify getOpponentNotify( ClientGame clientGame ) {
		if (clientGame == player1) {
			if (player2 != null) {
				return player2.getNotify();
			}
		} else {
			if (player1 != null) {
				return player1.getNotify();
			}
		}
		return null;
	}

	public void movePiece( ClientGame cGame, Location from, Location to) {
		
		PlayerPiece piece = gameboard[from.getXCoordinate()][from.getYCoordinate()];
		
		if (piece == null) {
			System.out.println("No piece on Location "+from.toString()+" to move!");
			return;
		}
		
		gameboard[to.getXCoordinate()][to.getYCoordinate()] = piece;
		piece.setLocation(to);
		gameboard[from.getXCoordinate()][from.getYCoordinate()] = null;
		
		// notify both sides
		player1.getNotify().notifyPieceMoved(piece, from, to);
		player2.getNotify().notifyPieceMoved(piece, from, to);
		
		changeTurnAndNotify();
	}

	/**
	 * Resolves an attack during a move. 
	 * @param cGame
	 * @param from
	 * @param to
	 */
	public void attackPiece( ClientGame cGame, Location from, Location to) {
		
		PlayerPiece attacker = gameboard[from.getXCoordinate()][from.getYCoordinate()];
		
		if (attacker == null) {
			System.out.println("No piece on Location "+from.toString()+" to attack with!");
			return;
		}
		
		PlayerPiece defender = gameboard[to.getXCoordinate()][to.getYCoordinate()];
		
		if (defender == null) {
			System.out.println("No piece on Location "+to.toString()+" to attack!");
			return;
		}

		if (attackerWins(attacker,defender)) {
			// replace the defender with the attacker
			gameboard[to.getXCoordinate()][to.getYCoordinate()] = attacker;	
			attacker.setLocation(to);
			gameboard[from.getXCoordinate()][from.getYCoordinate()] = null;	
			defender.setLocation(null);
			
		} else {
			// remove the attacker
			gameboard[from.getXCoordinate()][from.getYCoordinate()] = null;	
			attacker.setLocation(null);
		}
		
		// notify both sides
		player1.getNotify().notifyAttack(attacker, from, defender, to);
		player2.getNotify().notifyAttack(attacker, from, defender, to);

		if (defender.getPieceType() == StrategoPiece.FLAG) {
			player1.getNotify().notifyGameOver( (defender.getColor()==player2.getMyColor()) );
			player2.getNotify().notifyGameOver( (defender.getColor()==player1.getMyColor()) );
		} else {
			changeTurnAndNotify();
		}
	}

	private boolean attackerWins( PlayerPiece attacker, PlayerPiece defender ) {

		// check if attacking a bomb but the piece can kill bombs
		if (attacker.canKillaBomb() && defender.isaBomb()) {
			return true;
		}

		// check if attacking a marshall but the piece can kill marshalls
		if (attacker.canKillaMarshal() && defender.isaMarshal()) {
			return true;			
		}

		// compare rank - the lower rank is more powerful and wins
		if (attacker.getRank() < defender.getRank()) {
			return true;
		}
		return false;	
	}
}
